from langchain.tools import BaseTool
from pydantic import BaseModel, Field, field_validator
from typing import Dict, List, Optional, Union, Any, Type
from large_model.mode_interface.model_interface import ChatInterface
from langchain.output_parsers import PydanticOutputParser
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
from large_model.prompt_template import ExpectedResponse
from large_model.model_prompt.Interface_prompt import api_interface_model, prompt_template_doc
from utils.data_utils import read_interface_datas, clean_json_response
from dataclasses import dataclass, asdict
import json
import re


# 公共输入模型 - 字符串格式的接口文档
class APIStrDocument(BaseModel):
    api_doc: str = Field(..., description="字符串格式的接口文档内容")


class Assertion(BaseModel):
    """
        equals：断言状态码
        jsoniq：使用 JSONPath 进行 JSON 断言
        dict_eq：字典相等断言
    """
    equals: Dict[str, Union[int, str]] = None
    jsoniq: Dict[str, Any] = None
    dict_eq: Dict[str, Any] = None


# 定义测试参数, 这是单接口测试用例
class TestCase(BaseModel):
    feature: str = Field(..., description="allure装饰的feature：你需要定义功能模块名称")
    description: str = Field(..., description="allure装饰的description：你需要提供测试用例的详细描述")
    story: str = Field(..., description="allure装饰的story: 你需要定义用户故事或接口功能点")
    title: str = Field(..., description="allure装饰的title: 你需要定义测试用例标题")
    severity: str = Field(...,
                          description="allure装饰的severity: 你需要定义测试用例优先级(BLOCKER, CRITICAL, NORMAL, MINOR, TRIVIAL)")
    name: str = Field(...,
                      description="这是pytest的测试用例的名称， 你需要定义这个测试用例的函数名称， 如：test_01_method_name, test_02_method_name, "
                                  "这里必须是test_开头， method_nameb必须是英文")

    url: str = Field(..., description="请求地址, 这里的地址是接口的路径，不是域名")
    method: str = Field(..., description="HTTP 请求方法")
    headers: Optional[Dict[str, str]] = Field(default={'Content-Type': 'application/json'},
                                              description="请求头部信息, 如果参数有值，这里就填写对应的值，如果没有写，请求头则使用默认值")
    request: str = Field(...,
                         description="接口请求的传参方式， get则默认为paramsc传参，post则为json传参，如果接口文档中有request说明，则使用接口文档中的request传参方式")
    # 这里的传参方式是是自己定义的
    params: Dict[str, object] = Field(...,
                                      description="请求参数, 如果参数中存在小写true和小写的false, 都要替换为大写的True和大写的False, 如果参数中存在null，则替换为：'null'")
    # 扩展expected字段
    expected: List[Assertion] = Field(
        default_factory=list,
        description=ExpectedResponse
    )

    @field_validator('method')
    def validate_http_method(cls, v):
        valid_methods = ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
        if v.upper() not in valid_methods:
            raise ValueError(f'Invalid HTTP method: {v}')
        return v

    @field_validator('params')
    def validate_dict(cls, v):
        if not isinstance(v, dict):
            raise ValueError('Expected a dictionary')
        return v

    @field_validator('severity')
    def validate_severity(cls, v):
        valid_levels = ['BLOCKER', 'CRITICAL', 'NORMAL', 'MINOR', 'TRIVIAL']
        if v.upper() not in valid_levels:
            raise ValueError(f'Invalid severity level: {v}')
        return v.upper()


# 修复：为覆盖基类字段的属性添加类型注解
class InitialTestCaseGenerator(BaseTool):
    name: str = "initial_test_case_generator"
    description: str = "根据字符串格式的接口文档生成初始接口测试用例集合"
    args_schema: Type[BaseModel] = APIStrDocument  # 添加类型注解

    def _run(self, api_doc: str) -> str:
        """
        生成初始测试用例集A
        输入: 字符串格式的接口文档
        输出: JSON字符串格式的测试用例集合,不使用Markdown代码块包裹json
        """
        # 创建输出解析器
        parser = PydanticOutputParser(pydantic_object=TestCase)
        prompt = PromptTemplate(
            template=api_interface_model,
            input_variables=["template", "parser"],
            partial_variables={"format_instructions": parser.get_format_instructions()}
        )

        # 调用大模型生成测试用例
        response_model = ChatInterface().chat_model()

        # 链式输出
        chain = prompt | response_model
        result = chain.invoke({
            "template": api_doc,  # 直接使用传入的api_doc参数
            "parser": '{}，字典格式'  # 简化参数
        })
        print(result.content)
        try:
            cleaned_data = clean_json_response(result.content)
            data_dict = json.loads(cleaned_data)
        except:
            data_dict = json.loads(result.content)
        test_cases = []
        for data in data_dict:
            test_cases.append(data)
        return test_cases


# Tool 2: 用例补充增强器（处理字符串输入/输出）
class TestCaseEnhancer(BaseTool):
    name: str = "test_case_enhancer"
    description: str = "基于初始测试用例和接口文档进行场景补充，仅对接口测试用例生效"

    class InputModel(BaseModel):
        api_doc: str = Field(..., description="字符串格式的接口文档")
        initial_cases: List[TestCase] = Field(..., description="初始测试用例列表")

    args_schema: Type[BaseModel] = InputModel

    def to_dict(self):
        return asdict(self)  # 自动转换所有字段为字典

    def _run(self, api_doc: str, initial_cases: List[TestCase]):
        """
        在初始用例集A基础上补充生成B
        输入:
          - api_doc: 字符串格式接口文档
          - initial_cases: JSON字符串格式的测试用例
        输出: JSON字符串格式的增强后测试用例
        """
        if not isinstance(initial_cases, list):
            raise ValueError("响应不是有效的测试用例列表")

        # 创建输出解析器
        parser = PydanticOutputParser(pydantic_object=TestCase)

        # 构建提示词 - 使用命名占位符
        prompt_template = """
        [任务] 作为高级测试架构师，在现有用例基础上补充测试场景
        [接口文档]
        {api_doc}

        [现有用例] (共{case_count}个)
        初始的测试用例，用例为数组存放的JSON格式的的测试用例: 
        {test_cases}

        [补充要求]
        1. 保留所有有效初始用例
        2. 新增以下类型用例:
           - 异常参数: 错误类型/格式/空值
           - 安全测试: 注入攻击、越权访问
           - 边界极限: 超出范围的值
           - 并发场景: 高并发请求
        3. 补充后用例总数增加30%-50%
        4. 输出为JSON数组格式，包含原有和新增的测试用例集
        5. 只输出最后优化的最终的接口测试用例
        6. 接口数据格式如下:
        {format_instructions}
        7.最终输出一定要是纯JSON格式，列表嵌套JSON，必须以列表嵌套JSON的形式输出最终结果，不使用Markdown代码块包裹JSON。
        重要要求：
        1. 只输出纯净的 JSON 格式数据
        2. 不要包含任何 Markdown 标记或额外解释
        3. 确保 JSON 格式完整且有效
        4. 使用双引号而非单引号
        5. 确保所有字符串都有闭合引号
        """

        # 创建提示模板 - 声明需要的输入变量
        prompt = PromptTemplate(
            template=prompt_template,
            input_variables=["api_doc", "test_cases", "case_count"],
            partial_variables={"format_instructions": parser.get_format_instructions()}
        )

        print("调用深度思考")
        # 调用大模型补充用例
        response_model = ChatInterface().chat_model_reasoner()

        # 链式输出
        # chain = prompt | response_model
        # result = chain.invoke({})

        # 链式输出 - 提供所有需要的变量
        chain = prompt | response_model
        result = chain.invoke({
            "api_doc": api_doc,
            "test_cases": initial_cases,
            "case_count": len(initial_cases)
        })
        try:
            cleaned_data = clean_json_response(result.content)
            data_dict = json.loads(cleaned_data)
        except:
            data_dict = json.loads(result.content)

        print(data_dict)
        test_cases = []
        for data in data_dict:
            test_cases.append(data)
        return test_cases


# 定义输入模型（功能需求文档）
class FunctionalReqDocument(BaseModel):
    requirements_document: str = Field(..., description="功能需求文档字符串，描述用户操作流程、业务规则和系统行为")


# 功能测试用例的初始化生成
class FunctionalTestCaseGenerator(BaseTool):
    name: str = "functional_test_case_generator"
    description: str = "根据功能需求文档生成功能测试用例初始集合"
    args_schema: Type[BaseModel] = FunctionalReqDocument

    def _run(self, requirements_document: str) -> str:
        """
        生成功能测试用例集A
        输入:
            requirements_document: 字符串格式的需求文档
        输出: 固定格式的Xmind格式的测试用例
        """
        prompt = PromptTemplate(
            template=prompt_template_doc,
            input_variables=["document"]
        )
        print("测试用例初步生成")
        # 调用大模型生成测试用例
        model = ChatInterface().chat_model()

        chain = prompt | model
        result = chain.invoke({"document": requirements_document})
        return str(result.content)


# 定义功能测试用例评审的输入模型
class FunctionalTestCaseReviewInput(BaseModel):
    requirements_document: str = Field(..., description="功能需求文档字符串")
    initial_test_cases: str = Field(..., description="初始功能测试用例（Xmind格式字符串）")


# 功能测试用例评审工具
class FunctionalTestCaseReviewer(BaseTool):
    name: str = "functional_test_case_reviewer"
    description: str = "评审功能测试用例，确保覆盖需求并优化用例设计,仅对功能测试用例进行评审"
    args_schema: Type[BaseModel] = FunctionalTestCaseReviewInput

    def _run(self, requirements_document: str, initial_test_cases: str) -> str:
        """
        评审功能测试用例
        输入:
          - requirements_document: 功能需求文档
          - initial_test_cases: 初始功能测试用例(Xmind格式)
        输出: 优化后的Xmind格式测试用例
        """
        # 构建评审提示模板
        review_prompt_template = """
        [任务] 作为高级测试经理，请评审以下功能测试用例，并优化用例设计
        [功能需求文档]
        {requirements_document}

        [初始功能测试用例] (Xmind格式)
        {initial_test_cases}

        [评审要求]
        1. 检查测试用例是否完整覆盖需求文档的所有功能点
        2. 确保包含正向、异常和边界场景的测试用例
        3. 优化测试用例的表述，确保步骤清晰、预期结果明确
        4. 补充可能遗漏的测试场景
        5. 删除重复或不必要的测试用例
        6. 确保用例设计符合行业最佳实践

        [输出要求]
        1. 输出优化后的Xmind格式测试用例
        2. 只输出最终的Xmind内容，不要包含任何解释性文字
        3. 保持Xmind的结构化格式（使用缩进和符号表示层级）
        4. 不要添加额外的标题或说明
        5. 示例输出格式：
          根主题: [功能模块名]测试用例
            TC001: 正常登录
              前置: 用户已注册且账户状态正常
              步骤: 输入用户名"testuser"和密码"Test123!" | 预期: 登录成功并跳转首页
              步骤: 验证页面显示用户名 | 预期: 页面右上角显示"testuser"
            TC002: 密码错误
              前置: 用户已注册且账户状态正常
              步骤: 输入用户名"testuser"和密码"wrong" | 预期: 显示密码错误提示
              步骤: 验证登录按钮可再次点击 | 预期: 按钮状态为可点击
            TC003: 边界值测试
              前置: 用户名长度限制20字符
              步骤: 输入20字符用户名和正确密码 | 预期: 登录成功无截断
              
        重要：只输出纯净的Xmind格式内容，不要包含任何Markdown标记或额外解释。
        """
        print("测试用例评审")
        # 创建提示模板
        prompt = PromptTemplate(
            template=review_prompt_template,
            input_variables=["requirements_document", "initial_test_cases"]
        )

        # 调用大模型进行评审（使用深度思考模型）
        model = ChatInterface().chat_model_reasoner()

        # 链式处理
        chain = prompt | model
        result = chain.invoke({
            "requirements_document": requirements_document,
            "initial_test_cases": initial_test_cases
        })

        # 返回评审后的测试用例
        return result.content


# 在 __main__ 部分添加分阶段调试
if __name__ == "__main__":
    # 初始化工具
    tools = [
        # smart_functional_test_agent_runner
    ]

    # # 接口文档
    # api_doc = read_interface_datas()
    # api_doc_example = f"这是接口文档：{api_doc}" \
    #                   f"编写GetSeatChannelConf接口的用例"
    #
    # # ========= 阶段1: 初始用例生成 =========
    # print("\n===== 阶段1: 初始用例生成 =====")
    # result_A = tools[0]._run(api_doc=api_doc_example)
    # print(result_A)
    # print(f"A生成的测试用例数量{len(result_A)}")
    #
    # # ========= 阶段2: 用例补充 =========
    # print("\n===== 阶段2: 用例补充 =====")
    # result_B = tools[1]._run(api_doc=api_doc_example, initial_cases=result_A)
    # print(result_B)
    # print(f"B生成的测试用例数量{len(result_B)}")
    #
    # # 转换为YAML格式
    # yaml_output = yaml.dump(result_B, allow_unicode=True, sort_keys=False, indent=2, default_flow_style=False)
    #
    # # 保存到文件
    # output_path = r"D:\myfile\studydemo\apidemokeywordspyts\testdata\test_cases.yaml"
    # with open(output_path, 'w', encoding='utf-8') as f:
    #     f.write(yaml_output)
    #
    # print(f"成功生成 {len(result_B)} 个测试用例")
    # print(f"YAML文件已保存至: {output_path}")
    # from langchain_community.document_loaders import UnstructuredFileLoader
    # from utils.file_dir import get_doc_md_dir
    #
    # loader = UnstructuredFileLoader(get_doc_md_dir("王者aipass问答.docx"))
    # documents = loader.load()
    # #
    # # # 2. 直接使用文档内容
    # document_content = documents[0].page_content
    # print(document_content)
    # content = FunctionalTestCaseGenerator()._run(requirements_document=document_content)
    # content = smart_functional_test_agent_runner._run(get_doc_md_dir("王者aipass问答.docx"))
